/* gftp2 - Request
 *
 * Author: Lingtao Pan <freeplant@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "gftp.h"
#include <assert.h>

/**
 * gftp_request_new:
 * @log_func: An #gftp_logging_func.
 * 
 * Create a new #gftp_request.
 *
 * Retures: a #gftp_request.
 **/
gftp_request *
gftp_request_new (gftp_logging_func log_func)
{
    gftp_request *request;

    assert (log_func != NULL);

    request = g_malloc0 (sizeof (*request));
    request->datafd = -1;
    request->cachefd = -1;
    request->server_type = GFTP_DIRTYPE_OTHER;
    request->logging_function = log_func;
    return (request);
}

/**
 * gftp_request_destroy:
 * @request:
 * @free_request:
 *
 **/
void
gftp_request_destroy (gftp_request *request, int free_request)
{
    g_return_if_fail (request != NULL);

    gftp_request_disconnect (request);

    if (request->destroy != NULL)
        request->destroy (request);

    if (request->hostname)
        g_free (request->hostname);
    if (request->username)
        g_free (request->username);
    if (request->password) {
        memset (request->password, 0, strlen (request->password));
        g_free (request->password);
    }
    if (request->account) {
        memset (request->account, 0, strlen (request->account));
        g_free (request->account);
    }
    if (request->directory)
        g_free (request->directory);
    if (request->last_ftp_response)
        g_free (request->last_ftp_response);
    if (request->protocol_data)
        g_free (request->protocol_data);


    memset (request, 0, sizeof (*request));

    if (free_request)
        g_free (request);
    else {
        request->datafd = -1;
        request->cachefd = -1;
        request->server_type = GFTP_DIRTYPE_OTHER;
    }
}


void
gftp_request_set_hostname (gftp_request *request, const char *hostname)
{
    g_return_if_fail (request != NULL);
    g_return_if_fail (hostname != NULL);

    if (request->hostname)
        g_free (request->hostname);
    request->hostname = g_strdup (hostname);
    alltrim (request->hostname);
}

void 
gftp_request_set_username (gftp_request *request, const char *username)
{
    g_return_if_fail (request != NULL);

    if (request->username)
        g_free (request->username);

    if (username != NULL) 
        request->username = g_strdup (username);
    else
        request->username = NULL;
}

void 
gftp_request_set_password (gftp_request *request, const char *password)
{
    g_return_if_fail (request != NULL);

    if (request->password)
        g_free (request->password);

    if (password != NULL) 
        request->password = g_strdup (password);
    else
        request->password = NULL;
}

void
gftp_request_set_account (gftp_request * request, const char *account)
{
    g_return_if_fail (request != NULL);
    g_return_if_fail (account != NULL);

    if (request->account)
        g_free (request->account);
    request->account = g_strdup (account);
}

void
gftp_request_set_port (gftp_request * request, unsigned int port)
{
    g_return_if_fail (request != NULL);

    request->port = port;
}



int
gftp_request_set_directory (gftp_request *request, const char *directory)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);
    g_return_val_if_fail (directory != NULL, GFTP_EFATAL);

    if (request->datafd <= 0 && !request->always_connected) {
        if (directory != request->directory) {
            if (request->directory)
                g_free (request->directory);
            request->directory = g_strdup (directory);
        }
        return 0;
    } else if (request->chdir == NULL)
        return GFTP_EFATAL;
    return (request->chdir (request, directory));
}

void
gftp_request_setup_startup_directory (gftp_request *request, const char *dir)
{
    char *tempstr;

    if (*dir != '\0' &&  (tempstr = gftp_expand_path (dir)) != NULL) 
    {
        gftp_request_set_directory (request, tempstr);
        g_free (tempstr);
    }
}


int
gftp_request_connect (gftp_request *request)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    if (request->connect == NULL)
        return (GFTP_EFATAL);

    return (request->connect (request));
}


int  
gftp_request_is_connected (gftp_request *request)
{
    if ((request != NULL) && 
        ((request->datafd > 0) || (request->always_connected == 1)) )
        return 1;
    else
        return 0;
}

void
gftp_request_disconnect (gftp_request *request)
{
    g_return_if_fail (request != NULL);

    if (request->iconv_from_initialized) {
        g_iconv_close (request->iconv_from);
        request->iconv_from_initialized = 0;
    }

    if (request->iconv_to_initialized) {
        g_iconv_close (request->iconv_to);
        request->iconv_to_initialized = 0;
    }

    if (request->iconv_charset) {
        g_free (request->iconv_charset);
        request->iconv_charset = NULL;
    }

    request->cached = 0;
    if (request->disconnect == NULL)
        return;
    request->disconnect (request);
}

off_t
gftp_request_get_file (gftp_request *request, const char *filename,
                       off_t startsize)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    request->cached = 0;
    if (request->get_file == NULL)
        return (GFTP_EFATAL);

    return (request->get_file (request, filename, startsize));
}

int
gftp_request_put_file (gftp_request *request, const char *filename,
                       off_t startsize, off_t totalsize)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    request->cached = 0;
    if (request->put_file == NULL)
        return (GFTP_EFATAL);

    return (request->put_file (request, filename, startsize, totalsize));
}



int
gftp_request_abort_transfer (gftp_request *request)
{             
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    if (request->abort_transfer == NULL)
        return (GFTP_EFATAL);

    /* FIXME - end the transfer if it is not successful */
    return (request->abort_transfer (request));                                  
}


int
gftp_request_list_files_begin (gftp_request *request)
{
    char *remote_lc_time, *locret;
    int fd;
  
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

#if ENABLE_NLS
    remote_lc_time = gftp_config_get_string ("General", "remote_lc_time");
    if (remote_lc_time != NULL && *remote_lc_time != '\0')
        locret = setlocale (LC_TIME, remote_lc_time);
    else
        locret = setlocale (LC_TIME, NULL);

    if (locret == NULL) {
        locret = setlocale (LC_TIME, NULL);
        request->logging_function (gftp_logging_error, request,
                  _("Error setting LC_TIME to '%s'. Falling back to '%s'\n"),
                  remote_lc_time, locret);
    }
    if (remote_lc_time != NULL)
        g_free (remote_lc_time);
#else
    locret = _("<unknown>");
#endif

    request->cached = 0;
    if (request->use_cache && (fd = gftp_find_cache_entry (request)) > 0) {
        request->logging_function (gftp_logging_misc, request,
                  _("Loading directory listing %s from cache (LC_TIME=%s)\n"),
                  request->directory, locret);

        request->cachefd = fd;
        request->cached = 1;
        return (0);
    } else if (request->use_cache) {
        request->logging_function (gftp_logging_misc, request,
                  _("Loading directory listing %s from server (LC_TIME=%s)\n"),
                  request->directory, locret);

        request->cachefd = gftp_new_cache_entry (request);
        request->cached = 0; 
    }

    if (request->list_files == NULL)
        return (GFTP_EFATAL);

    return (request->list_files (request));
}

int
gftp_request_list_files_next (gftp_request * request, const char *filespec,
                             gftp_file * fle)
{
    char *slashpos, *tmpfile, *utf8;
    size_t destlen;
    int fd, ret;

    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    if (request->get_next_file == NULL)
        return (GFTP_EFATAL);

    if (request->cached && request->cachefd > 0)
        fd = request->cachefd;
    else
        fd = request->datafd;

    memset (fle, 0, sizeof (*fle));

    do {
        gftp_file_destroy (fle, 0);
        ret = request->get_next_file (request, fle, fd);
        if (fle->file != NULL && (slashpos = strrchr (fle->file, '/')) != NULL) {
            if (*(slashpos + 1) == '\0') {
                gftp_file_destroy (fle, 0);
                continue;
            }

            *slashpos = '\0';
            tmpfile = g_strdup (slashpos + 1);
            
            if (strcmp (fle->file, request->directory) != 0)
                request->logging_function (gftp_logging_error, request,
                                   _("Warning: Stripping path off of file '%s'. The stripped path (%s) doesn't match the current directory (%s)\n"),
                                   tmpfile, fle->file, request->directory,
                                   g_strerror (errno));
      
            g_free (fle->file);
            fle->file = tmpfile;
        }

        if (ret >= 0 && fle->file != NULL) {
            if (g_utf8_validate (fle->file, -1, NULL))
                fle->filename_utf8_encoded = 1;
            else {
                utf8 = gftp_filename_to_utf8 (request, fle->file, &destlen);
                if (utf8 != NULL) {
                    g_free (fle->file);
                    fle->file = utf8;
                    fle->filename_utf8_encoded = 1;
                }
            }
        }

        if (ret >= 0 && !request->cached && request->cachefd > 0 && 
            request->last_dir_entry != NULL)
        {
            if (gftp_fd_write (request, request->last_dir_entry,
                         request->last_dir_entry_len, request->cachefd) < 0)
            {
                request->logging_function (gftp_logging_error, request,
                                   _("Error: Cannot write to cache: %s\n"),
                                   g_strerror (errno));
                close (request->cachefd);
                request->cachefd = -1;
            }
        }
    } while (0);

    return (ret);
}

int
gftp_request_end_transfer (gftp_request *request)
{
    int ret;

    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    if (!request->cached && 
        request->end_transfer != NULL)
        ret = request->end_transfer (request);
    else
        ret = 0;

    if (request->cachefd > 0) {
        close (request->cachefd);
        request->cachefd = -1;
    }

    if (request->last_dir_entry)  {
        g_free (request->last_dir_entry);
        request->last_dir_entry = NULL;
        request->last_dir_entry_len = 0;
    }
    
    return (ret);
}

int
gftp_request_stat_filename (gftp_request * request, const char *filename, 
                            mode_t * mode, off_t * filesize)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);
    g_return_val_if_fail (filename != NULL, GFTP_EFATAL);

    if (request->stat_filename != NULL)
        return (request->stat_filename (request, filename, mode, filesize));
    else
        return (0);
}

off_t
gftp_request_get_file_size (gftp_request * request, const char *filename)
{
    g_return_val_if_fail (request != NULL, 0);

    if (request->get_file_size == NULL)
        return (0);
    return (request->get_file_size (request, filename));
}

int
gftp_request_parse_url (gftp_request *request, const char *url)
{
    char *pos, *endpos, *default_protocol, *new_url;
    gftp_logging_func logging_function;
    const char *clear_pos;
    int i, ret;

    g_return_val_if_fail (request != NULL, GFTP_EFATAL);
    g_return_val_if_fail (url != NULL, GFTP_EFATAL);

    logging_function = request->logging_function;
    gftp_request_destroy (request, 0);
    request->logging_function = logging_function;

    for (clear_pos = url;
         *clear_pos == ' ' || *clear_pos == '\t';
         clear_pos++);

    new_url = g_strdup (clear_pos);

    for (pos = new_url + strlen (new_url) - 1;
         *pos == ' ' || *pos == '\t';
         pos--)
        *pos = '\0';

    /* See if the URL has a protocol... */
    if ((pos = strstr (new_url, "://")) != NULL) {
        *pos = '\0';

        for (i = 0; gftp_protocols[i].url_prefix; i++) {
            if (strcmp (gftp_protocols[i].url_prefix, new_url) == 0)
                break;
        }

        if (gftp_protocols[i].url_prefix == NULL) {
            request->logging_function (gftp_logging_error, NULL, 
                         _("The protocol '%s' is currently not supported.\n"),
                         new_url);
            g_free (new_url);
            return (GFTP_EFATAL);
        }

        *pos = ':';
        pos += 3;
    } else {
        default_protocol  = gftp_config_get_string ("General", 
                                                    "default_protocol");

        i = GFTP_FTP_NUM;
        if (default_protocol != NULL && *default_protocol != '\0') {
            for (i = 0; gftp_protocols[i].url_prefix; i++) {
                if (strcmp (gftp_protocols[i].name, default_protocol) == 0)
                    break;
            }
        }

        if (gftp_protocols[i].url_prefix == NULL) {
            request->logging_function (gftp_logging_error, NULL, 
                                       _("The protocol '%s' is currently not supported.\n"),
                                       default_protocol);
            g_free (new_url);
            return (GFTP_EFATAL);
        }

        pos = new_url;

        if (default_protocol != NULL)
            g_free (default_protocol);
    }

    if ((ret = gftp_protocols[i].init (request)) < 0) {
        gftp_request_destroy (request, 0);
        return (ret);
    }

    if ((endpos = strchr (pos, '/')) != NULL) {
        gftp_request_set_directory (request, endpos);
        *endpos = '\0';
    }

    if (request->parse_url != NULL) {
        ret = request->parse_url (request, new_url);
        g_free (new_url);
        return (ret);
    }

    if (*pos != '\0') {
        if (endpos == NULL)
            endpos = pos + strlen (pos) - 1;
        else
            endpos--;

        for (; isdigit (*endpos); endpos--);

        if (*endpos == ':' && isdigit (*(endpos + 1)))  {
            gftp_request_set_port (request, strtol (endpos + 1, NULL, 10));
            *endpos = '\0';
        }

        if ((endpos = strrchr (pos, '@')) != NULL) {
            gftp_request_set_hostname (request, endpos + 1);
            *endpos = '\0';

            if ((endpos = strchr (pos, ':')) != NULL) {
                *endpos = '\0';
                gftp_request_set_username (request, pos);
                gftp_request_set_password (request, endpos + 1);
            } else {
                gftp_request_set_username (request, pos);
                gftp_request_set_password (request, "");
            }
        } else
            gftp_request_set_hostname (request, pos);
    }

    g_free (new_url);
    return (0);
}

ssize_t 
gftp_request_get_next_file_chunk (gftp_request *request, char *buf, size_t size)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);
    g_return_val_if_fail (buf != NULL, GFTP_EFATAL);

    if (request->get_next_file_chunk != NULL)
        return (request->get_next_file_chunk (request, buf, size));

    return (request->read_function (request, buf, size, request->datafd));
}

ssize_t 
gftp_request_put_next_file_chunk (gftp_request * request, 
                                  char *buf, size_t size)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);
    g_return_val_if_fail (buf != NULL, GFTP_EFATAL);

    if (request->put_next_file_chunk != NULL)
        return (request->put_next_file_chunk (request, buf, size));

    return (request->write_function (request, buf, size, request->datafd));
}


int
gftp_request_remove_directory (gftp_request * request, const char *directory)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    if (request->rmdir == NULL)
        return (GFTP_EFATAL);
    return (request->rmdir (request, directory));
}


int
gftp_request_remove_file (gftp_request *request, const char *file)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);
    
    if (request->rmfile == NULL)
        return (GFTP_EFATAL);
    return (request->rmfile (request, file));
}

int
gftp_request_make_directory (gftp_request *request, const char *directory)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);
    
    if (request->mkdir == NULL)
        return (GFTP_EFATAL);
    
    return (request->mkdir (request, directory));
}


int
gftp_request_rename_file (gftp_request *request, const char *oldname,
                  const char *newname)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    if (request->rename == NULL)
        return (GFTP_EFATAL);
    
    return (request->rename (request, oldname, newname));
}

int
gftp_request_chmod (gftp_request *request, const char *file, mode_t mode)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    if (request->chmod == NULL)
        return (GFTP_EFATAL);

    mode &= S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX;
    return (request->chmod (request, file, mode));
}


int
gftp_request_set_file_time (gftp_request *request, const char *file, 
                            time_t datetime)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    if (request->set_file_time == NULL)
        return (GFTP_EFATAL);
    return (request->set_file_time (request, file, datetime));
}


int
gftp_request_site_cmd (gftp_request *request, int specify_site, 
                       const char *command)
{
    g_return_val_if_fail (request != NULL, GFTP_EFATAL);

    if (request->site == NULL)
        return (GFTP_EFATAL);
    return (request->site (request, specify_site, command));
}
